package com.aica.service.impl;

import com.aica.bo.UserAndRoleBo;
import com.aica.bo.UserMessageBo;
import com.aica.bo.response.GetUserInformationBo;
import com.aica.bo.response.UploadAvatarBo;
import com.aica.constant.*;
import com.aica.entity.*;
import com.aica.mapper.*;
import com.aica.service.UserService;
import com.aica.util.EmailUtil;
import com.aica.util.FileUtil;
import com.aica.util.RedisUtil;
import com.aica.util.TypeUtil;
import com.aica.vo.request.*;
import com.aica.vo.ResultVo;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;

import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.util.*;

/**
 * 用户相关服务类
 * @author by
 * @since 2021/6/7
 */
@Slf4j
@Service
public class UserServiceImpl implements UserService {

    @Resource
    private RedisUtil redisUtil;
    @Resource
    private FileUtil fileUtil;
    @Resource
    private UserMapper userMapper;
    @Resource
    private RoleMapper roleMapper;
    @Resource
    private PersonalSignatureMapper personalSignatureMapper;
    @Resource
    private PunchInRecordMapper punchInRecordMapper;
    @Resource
    private UserRoleMapper userRoleMapper;

    @Resource
    private PasswordEncoder passwordEncoder;


    @Override
    public UserAndRoleBo selectUserAndRoleByUsername(String username) {
        //查询用户信息
        User user = userMapper.selectOne(new QueryWrapper<User>().eq("username", username));
        //用户不存在，返回空
        if(user==null){
            return null;
        }
        //查询用户角色关系(暂时不判断角色身份)
        //List<Integer> roleIds = userRoleMapper.selectList(new QueryWrapper<UserRole>().eq("user_id", user.getId())).stream().map(UserRole::getRoleId).collect(Collectors.toList());
        List<Integer> roleIds = new ArrayList<>(1);
        roleIds.add(1);

        //查询角色描述(暂时不判断角色身份)
        //List<Role> roles = roleMapper.selectBatchIds(roleIds);
        List<Role> roles = new ArrayList<>(1);
        roles.add(Role.getOne());

        //返回整合对象
        return new UserAndRoleBo(user,roleIds,roles);

    }

    @Override
    public UserMessageBo getUserMessageFromDataBase(String username) {
        if(username==null){
            return null;
        }
        //todo 待修改，后续需要查询其他表的信息，可调用其他查询封装好的方法，最后整合到UserMessageBo内
        UserAndRoleBo userAndRoleBo = selectUserAndRoleByUsername(username);
        //结果判空
        if(userAndRoleBo==null){
            return null;
        }
        //用户的个性签名
        PersonalSignature personalSignature = personalSignatureMapper.selectOne(
                new QueryWrapper<PersonalSignature>().eq("user_id", userAndRoleBo.getUserId()));
        //获得用户打卡记录
        List<PunchInRecord> punchInRecordList = punchInRecordMapper.selectList(
                new QueryWrapper<PunchInRecord>().eq("user_id", userAndRoleBo.getUserId()));


        return new UserMessageBo(userAndRoleBo,personalSignature,punchInRecordList);
    }

    /**
     * 已测试
     */
    @Override
    public UserMessageBo getUserMessageFromRedis(String username) {
        if(username==null){
            return null;
        }
        UserMessageBo userMessageBo = (UserMessageBo) redisUtil.get(StringBuilderConstant.USER_MESSAGE_ + username);
        //如果数据不存在则查询数据库获得数据，同时保存至缓存中
        return userMessageBo==null? refreshUserMessageOnRedis(username) : userMessageBo;
    }

    @Override
    public UserMessageBo refreshUserMessageOnRedis(String username) {
        //拼接key
        String userMessageKey = StringBuilderConstant.USER_MESSAGE_ + username;
        //获得数据库最新消息
        UserMessageBo userMessageFromDataBase = getUserMessageFromDataBase(username);
        if(userMessageFromDataBase==null){
            //清除缓存
            redisUtil.del(userMessageKey);
            return null;
        }

        //覆盖性写入信息
        redisUtil.set(
                userMessageKey,
                userMessageFromDataBase,
                RedisTimeConstant.USER_MESSAGE_TIME_OUT
        );
        //读取
        return (UserMessageBo)redisUtil.get(userMessageKey);
        //直接返回
        //return userMessageFromDataBase;
    }

    /**
     * 已测试
     */
    @Override
    public ResultVo<Object> sendEmail(SendEmailCodeVo sendEmailCodeVo) {
        //获取邮箱地址
        String email = sendEmailCodeVo.getEmail();
        //声明验证码
        String codeSend;
        //拼接key
        String emailCodeKey = StringBuilderConstant.EMAIL_VERIFICATION_REGISTER_CODE_ + email;
        //未生成验证码
        if (! redisUtil.hasKey(emailCodeKey) ){
            //生成随机码
            Random random = new Random(System.currentTimeMillis());
            //[100000,999999)
            Integer randomCode = 100000 + random.nextInt(900000);
            codeSend = randomCode+"";

            //设置15分钟过时
            redisUtil.set(emailCodeKey, codeSend,60*15);
        }
        //获得存储值
        codeSend = (String) redisUtil.get(emailCodeKey);

        //日志打印验证码方便测试
        log.info("邮箱："+email + " 本次的验证码为-->： "+codeSend + " ，已发送");

        //发送邮件
        boolean success = EmailUtil.sendVerificationCode(email, codeSend);
        //如果成功返回200；如果失败说明发送邮箱失败，返回500
        return success ? ResultConstant.OK : ResultConstant.SYSTEM_ERROR;
    }

    /**
     * 已验证
     */
    @Override
    public ResultVo<Object> register(RegisterVo registerVo) {
        //查询用户名
        User userOfName = userMapper.selectOne(new QueryWrapper<User>().eq(SqlColumnConstant.USERNAME, registerVo.getUsername()));
        //用户名已存在
        if(userOfName!=null){
            return ResultConstant.USER_NAME_EXIST;
        }
        //查询邮箱
        User userOfEmail = userMapper.selectOne(new QueryWrapper<User>().eq(SqlColumnConstant.EMAIL, registerVo.getEmail()));
        //该邮箱已注册账号
        if(userOfEmail!=null){
            return ResultConstant.EMAIL_EXIST;
        }
        //拼接key
        String emailCodeKey = StringBuilderConstant.EMAIL_VERIFICATION_REGISTER_CODE_ + registerVo.getEmail();
        //获取存储的验证码
        String emailCode = (String)redisUtil.get(emailCodeKey);

        //日志打印验证码
        log.info("邮箱："+registerVo.getEmail() + " 本次的验证码："+emailCode);

        //为空
        if(StringUtils.isEmpty(emailCode)){
            //日志打印验证码状态
            log.info("邮箱："+registerVo.getEmail() + " 本次的验证码校验失败，验证码未发送");
            //验证码错误或未发送
            return ResultConstant.EMAIL_CODE_BAD_OR_EMPTY;
        }
        //清除缓存
        redisUtil.del(emailCodeKey);
        //验证码不正确
        if(! emailCode.equals(registerVo.getEmailCode()) ){
            //日志打印验证码状态
            log.info("邮箱："+registerVo.getEmail() + " 本次的验证码校验失败，验证码输入错误，该验证码失效");
            //验证码错误或未发送 todo 这里后面再看要不要拆分成 验证码不正确 与 验证码为空 2种状态码
            return ResultConstant.EMAIL_CODE_BAD_OR_EMPTY;
        }

        User user = new User(
                null,
                null,
                registerVo.getUsername(),
                //nick name初始化为与用户名一致
                registerVo.getUsername(),
                //加密密码
                //todo 这里使用了空加密，即使用明文存储方便测试，后续上线需修改为密文存储
                passwordEncoder.encode(registerVo.getPassword()),
                registerVo.getPhoneNumber(),
                registerVo.getEmail(),
                null,
                null,
                //记录注册时间
                new Date()
        );
        //插入用户信息
        int i1 = userMapper.insert(user);
        //插入用户角色关系
        //todo 这里暂时默认使用角色1，后续根据需求修改
        int i2 = userRoleMapper.insert(new UserRole(null, user.getId(), 1));
        //todo 这里暂时默认插入一条个性签名，后续根据需求修改
        int i3 = personalSignatureMapper.insert(new PersonalSignature(null, user.getId(), "这个人很懒，什么都没留下"));

        return  ((1==i1)&&(1==i2)&&(1==i3)) ? ResultConstant.OK : ResultConstant.SYSTEM_ERROR;
    }

    /**
     * 已测试
     */
    @Override
    public ResultVo<Object> changePasswordByOld(
            ChangePasswordByOldVo changePasswordByOldVo,
            UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken) {
        //获得用户名
        String name = usernamePasswordAuthenticationToken.getName();
        log.info(name + "正在使用旧密码修改密码。。。");

        if(StringUtils.isEmpty(name)) {
            //认证信息过期
            log.info(name + "usernamePasswordAuthenticationToken 已失效，系统错误");
            //todo 看可不可达，备选 ResultConstant.TOKEN_EXPIRE
            return ResultConstant.SYSTEM_ERROR;
        }

        //查询用户基础信息（这里直接使用数据库，后续优化可以把用户密码加入redis缓存中）
        User realUserData = userMapper.selectOne(new QueryWrapper<User>().eq("username", name));

        //如果原始密码在编码后与存储中的编码密码不匹配
        if(! passwordEncoder.matches(
                changePasswordByOldVo.getOldPassword(),realUserData.getPassword())){
            //旧密码错误
            return ResultConstant.CODE_ERROR;
        }


        //构建更新sql语句where条件
        UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
        updateWrapper.eq("id",realUserData.getId());
        //构建set值
        User user = new User();
        user.setPassword(passwordEncoder.encode(changePasswordByOldVo.getNewPassword()));
        //更新密码
        int update = userMapper.update(user, updateWrapper);
        //更新失败
        if(update!=1){
            //系统错误
            return ResultConstant.SYSTEM_ERROR;
        }

        //清除redis中用户信息，用户当前token，用户名与ID关系
        redisUtil.del(
                StringBuilderConstant.USER_MESSAGE_ + name,
                StringBuilderConstant.USER_TOKEN_ + name,
                StringBuilderConstant.USER_ID_ + name
        );
        log.info("用户" +name +"的新密码为："+changePasswordByOldVo.getNewPassword());

        //更新成功，同时使密匙过期
        return ResultConstant.SUCCESS_AND_TOKEN_EXPIRE;
    }

    /**
     * 已测试
     */
    @Override
    public ResultVo<Object> getUserInformation(
            UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken) {
        //获得用户名
        String username = usernamePasswordAuthenticationToken.getName();
        log.info(username + "正在获取用户个人详细信息。。。");

        if(StringUtils.isEmpty(username)) {
            //认证信息过期
            log.info(username + "usernamePasswordAuthenticationToken 已失效，系统错误");
            //todo 备选 ResultConstant.TOKEN_EXPIRE
            return ResultConstant.SYSTEM_ERROR;
        }

        UserMessageBo userMessage = getUserMessageFromRedis(username);
        //判空
        if(userMessage==null){
            //认证信息过期
            log.info(username + ": 该用户数据丢失");
            //todo 备选 ResultConstant.TOKEN_EXPIRE
            return ResultConstant.SYSTEM_ERROR;
        }
        GetUserInformationBo userInformationBo = new GetUserInformationBo();
        //拷贝相同字段的值
        BeanUtils.copyProperties(userMessage,userInformationBo);

        return new ResultVo<>(userInformationBo);

    }

    /**
     * 已测试
     */
    @Override
    public ResultVo<Object> updateInformation(
            UpdateInformationVo updateInformationVo,
            UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken) {

        //获得用户名
        String username = usernamePasswordAuthenticationToken.getName();
        log.info(username + "正在更新用户个人详细信息。。。");
        Integer userId = (Integer)redisUtil.get(StringBuilderConstant.USER_ID_ + username);

        //todo 格式校验在实体类中过滤

        //性别格式转换
        Boolean gender = TypeUtil.genderIntegerToBoolean(updateInformationVo.getGender()) ;

        //更新数据库信息
        //更新user表
        UpdateWrapper<User> updateWrapper1 = new UpdateWrapper<User>()
                .eq("id", userId)
                .set("nick_name",updateInformationVo.getNickName())
                .set("telephone",updateInformationVo.getPhoneNumber())
                .set("gender",gender);
        int update1 = userMapper.update(new User(), updateWrapper1);
        //更新PersonalSignature个性签名表
        UpdateWrapper<PersonalSignature> updateWrapper2 = new UpdateWrapper<PersonalSignature>()
                .eq("user_id", userId)
                .set("content",updateInformationVo.getPersonalSignature());
        int update2 = personalSignatureMapper.update(new PersonalSignature(), updateWrapper2);

        return  ((1==update1)&&(1==update2)) ? ResultConstant.OK : ResultConstant.SYSTEM_ERROR;
    }


    /**
     * 已测试
     */
    @Override
    public ResultVo<Object> refreshAvatar(
            UploadAvatarVo uploadAvatarVo,
            UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken) {
        //获取用户名
        String username = usernamePasswordAuthenticationToken.getName();
        //获取随机文件名
        //String path = System.currentTimeMillis() + ".jpeg";
        //覆盖原本的头像图片
        String path = "avatar.jpeg";
        //保存文件
        Boolean uploadSuccess = fileUtil.upLoadPhoto(uploadAvatarVo.getAvatar(), username, path);

        if(! uploadSuccess){
            return ResultConstant.FILE_UPLOAD_FALSE;
        }
        //更新数据库信息
        UpdateWrapper<User> updateWrapper = new UpdateWrapper<User>().eq("username", username);
        User user = new User();
        user.setAvatar(fileUtil.getFolder() + "/"+username+"/"+path);
        int update = userMapper.update(user, updateWrapper);
        //更新缓存数据
        UserMessageBo userMessageBo = refreshUserMessageOnRedis(username);

        if(update!=1 || userMessageBo==null){
            return ResultConstant.SYSTEM_ERROR;
        }
        //返回头像路径
        return new ResultVo<>(
                new UploadAvatarBo(userMessageBo.getAvatar())
        );
    }

}
